/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author            Notes
 * 20022-03015    bucthuang         first implementation
 */
#include <stdint.h>
#include <rtthread.h>
#include <rtdevice.h>
#include <stdlib.h>

#define NORMAL                  (0)                             //正常输出电流模式
#define FORCE                   (1)                             //强制未校正状态输出电流

#define PWM_420_NAME            "tim3pwm1"
#define RT_USING_INVERTER       (1)                             //使用了反相器,占空比应该反过来
#define PWM_DEFAULT_PERIOD      (10000)                         //实测知道是10ms,底层就不去改了

typedef struct
{
    rt_uint32_t pulse;                                          //当前值
    rt_uint32_t mv_250;                                         //250R电阻对应的mv值,ma*250=ua/4
} rt_pwm420_dat;

typedef struct
{
    float k;                                                    //校正参数
    float b;                                                    //校正参数
    rt_pwm420_dat ma_04_dat;                                    //存放4ma标定数据
    rt_pwm420_dat ma_20_dat;                                    //存放20ma标定数据
} rt_calibrat_type;

typedef struct
{
    struct rt_device_pwm *dev;

    rt_uint8_t calibrat_flag;                                   //是否完成校正，如果完成了，要按校正的参数进行输出
    rt_uint8_t stop;                                            //是否停止外设，如果停止了，要重启动

    rt_uint32_t pulse;

} rt_pwm420_type;

static rt_calibrat_type calib_mgr = { 0 };
static rt_pwm420_type pwm420_mgr = { 0 };

static void calc_kb(void)
{
    //250R,ma*250 = mv_250-->ma=mv_250/250-->ua=mv_250*4
    //y=kx+b;//x=ua
    float x0, x1, y0, y1, k, b;

    rt_pwm420_dat *pdat = &calib_mgr.ma_04_dat;

    x0 = pdat[0].mv_250;
    y0 = pdat[0].pulse;
    x1 = pdat[1].mv_250;
    y1 = pdat[1].pulse;

    k = (y1 - y0) / ((x1 - x0) * 4);
    b = y0 - k * x0 * 4;

    calib_mgr.k = k;
    calib_mgr.b = b;
}

static rt_uint32_t get_calib_pls(rt_uint32_t ua)
{
    float k = calib_mgr.k;
    float b = calib_mgr.b;
    return (rt_uint32_t) (k * ua + b);
}

static rt_uint32_t get_no_calib_pls(rt_uint32_t ua)
{
    //25ma=10000,1ma=400;1ua = 0.4;
    rt_uint32_t pulse = ua;

    //没有使用反相器时，直接计算占空就可以
    pulse <<= 2;
    pulse /= 10;

#if (PWM_DEFAULT_PERIOD != 10000)
    pulse = pulse * PWM_DEFAULT_PERIOD / 10000;
#endif

    if (pulse > PWM_DEFAULT_PERIOD)
    {
        pulse = PWM_DEFAULT_PERIOD;
    }

#if (RT_USING_INVERTER == 1)
    //使用了反相器，就要反过来了
    pulse = PWM_DEFAULT_PERIOD - pulse;
#endif

    return pulse;
}

static rt_uint32_t get_pls(rt_uint32_t ua, rt_uint8_t flag)
{
    rt_uint32_t temp = ua;
    if (flag)
    {
        //完成校正
        return get_calib_pls(temp);
    }
    else
    {
        //未校正 //25mA = 10000,1mA=40
        return get_no_calib_pls(temp);
    }
}

static void pwm_420_out(rt_uint32_t ua, rt_uint8_t force)
{
    rt_pwm420_type *pmgr = &pwm420_mgr;
    struct rt_device_pwm *dev = pmgr->dev;

    rt_uint32_t new_pulse = 0;
    rt_uint8_t calibrat_flag = 0;

    if (!dev)
    {
        rt_kprintf("can not find the pwm device\n");
        return;
    }

    if (force == NORMAL)
    {
        calibrat_flag = pmgr->calibrat_flag;    //按实际标定结果输出
    }
    else
    {
        calibrat_flag = 0;    //强制输出4ma,20ma用来校正的
    }

    new_pulse = get_pls(ua, calibrat_flag);

    if (new_pulse == 0)
    {
        //stop
        rt_pwm_disable(dev, 1);
        pmgr->stop = 1;
        pmgr->pulse = new_pulse;
        return;
    }

    if (new_pulse != pmgr->pulse)
    {
        rt_pwm_set(dev, 1, PWM_DEFAULT_PERIOD, new_pulse);
        pmgr->pulse = new_pulse;
    }

    if (pwm420_mgr.stop == 1)
    {
        rt_pwm_enable(dev, 1);
        pwm420_mgr.stop = 0;
    }
}

static int pwm420(int argc, char **argv)
{
    struct rt_device_pwm *dev = pwm420_mgr.dev;

    if (dev == RT_NULL)
    {
        dev = (struct rt_device_pwm *) rt_device_find(PWM_420_NAME);
        if (dev == RT_NULL)
        {
            rt_kprintf("can not find timepwm1\n");
            return -1;
        }
        pwm420_mgr.dev = dev;
        rt_pwm_set(dev, 1, PWM_DEFAULT_PERIOD, (PWM_DEFAULT_PERIOD >> 1));         //100Hz 50%
        rt_pwm_enable(dev, 1);
        rt_kprintf("timepwm1 init ok!\n");
    }

    //pwm_420 out <x-mA>
    if (rt_memcmp(argv[1], "out", 3) == 0)             //正常输出mA
    {
        if (argc == 3)
        {
            double temp = atof(argv[2]);
            rt_uint32_t new_pulse = 0;

            //太小了直接关掉外设,记录状态
            if (temp < 0.0001)             //0.1uA
            {
                //stop the pwm
                rt_pwm_disable(dev, 1);
                pwm420_mgr.stop = 1;
                return 0;
            }
            //4~20mA嘛,不能太大了
            if (temp > 24)
            {
                temp = 24;
            }

            temp *= 1000;                //uA

            pwm_420_out((rt_uint32_t) temp, NORMAL);

            return 0;
        }
        else
        {
            goto _error;
        }

    }
    else if (rt_memcmp(argv[1], "force", 5) == 0)      //强制输出 4ma,20ma
    {
        int ma = atoi(argv[2]);

        if ((ma == 4) || (ma == 20))
        {
            pwm_420_out(ma * 1000, FORCE);
            return 0;
        }
        goto _error;
    }
    else if (rt_memcmp(argv[1], "calib", 5) == 0)
    {
        if (argc == 4)
        {
            int ma = atoi(argv[2]);
            int mv = atoi(argv[3]);

            if (ma == 4)
            {
                calib_mgr.ma_04_dat.pulse = pwm420_mgr.pulse;
                calib_mgr.ma_04_dat.mv_250 = mv;
            }
            else if (ma == 20)
            {
                calib_mgr.ma_20_dat.pulse = pwm420_mgr.pulse;
                calib_mgr.ma_20_dat.mv_250 = mv;
            }
            else
            {
                goto _error;
            }
            return 0;
        }
        if (argc == 3)
        {
            if (rt_memcmp(argv[2], "ok", 2) == 0)
            {
                pwm420_mgr.calibrat_flag = 1;
                calc_kb();
                //TODO:save to flash/eeprom
                goto _calibok;
            }
            else if (rt_memcmp(argv[2], "drop", 4) == 0)
            {
                pwm420_mgr.calibrat_flag = 0;
                ////TODO:clear the dat
                return 0;
            }
        }
    }
    else if (rt_memcmp(argv[1], "stat", 4) == 0)
    {
        goto _calibok;
    }

    _error: rt_kprintf("Usage: pwm420 out 4.0\n");
    rt_kprintf("       pwm420 out <x-mA>\n");
    rt_kprintf("       pwm420 force 4/20\n");
    rt_kprintf("       pwm420 calib 4/20 <x-mV>\n");
    rt_kprintf("       pwm420 calib ok/drop\n");
    rt_kprintf("       pwm420 stat\n");
    return -1;

    _calibok:
    if (pwm420_mgr.calibrat_flag)
    {
        rt_kprintf("pwm40 calib ok\n");
    }
    rt_kprintf("\n--------------------------------\n");
    rt_kprintf("k=%d\n", (rt_int32_t) (calib_mgr.k * 1000));
    rt_kprintf("b=%d\n", (rt_int32_t) (calib_mgr.b * 1000));
    rt_kprintf("[p,v]:(%d,%d),(%d,%d)\n", calib_mgr.ma_04_dat.pulse, calib_mgr.ma_04_dat.mv_250,
            calib_mgr.ma_20_dat.pulse, calib_mgr.ma_20_dat.mv_250);
    rt_kprintf("\n--------------------------------\n");

    return 0;
}
MSH_CMD_EXPORT(pwm420, pwm for 4 - 20mA);
